﻿using System;
using System.Collections.Generic;
using System.Text;
using Vortex.Wrapper.Direct3D;
using Vortex.Wrapper;
using Vortex.Debugging;
using Vortex.Drawing.Performance;
using System.Runtime.InteropServices;

namespace Vortex.Drawing {

	/// <summary>
	/// Encapsulates operations with vertex/index buffers using vertex bufferization
	/// </summary>
	unsafe class BufferedVertexStream : Component {
		Direct3DVertexBuffer9 _vertexBuffer;
		Direct3DIndexBuffer9 _indexBuffer;
		Direct3DVertexDeclaration9 _vertexDeclaration;
		//data pointers
		Vertex2D* _vertexData;
		ushort* _indexData;
		
		//maximum vertices/indices in buffer
		int _maxVertexCount = 32768, _maxIndexCount = 65536;

		//type of data in current batch
		PrimitiveType _primitiveType;
		//current counts
		int _vertexCount, _indexCount;
		/// <summary>Flag that batch is currently is opened</summary>
		bool _batchIsOpened;

		public BufferedVertexStream(DrawingDevice device)
			: base(device) {
				
			//initialize on creation
			Initialize();
		}

		/// <summary>
		/// Gets a value indicating whether this vertex stream is empty.
		/// </summary>
		/// <value><c>true</c> if this vertex stream is empty; otherwise, <c>false</c>.</value>
		public bool IsEmpty {
			get { return _indexCount == 0; }
		}

		/// <summary>
		/// Opens this vertex stream.
		/// </summary>
		public void Open() {
			HResult result;
			result = _d3dDevice.SetStreamSource(0, _vertexBuffer, 0, (uint)sizeof(Vertex2D));
			Watcher.AssertCritical(result, "Unable to set stream source");

			result = _d3dDevice.SetIndices(_indexBuffer);
			Watcher.AssertCritical(result, "Unable to set indices");

			result = _d3dDevice.SetVertexDeclaration(_vertexDeclaration);
			Watcher.AssertCritical(result, "Unable to set vertex declaration");
		}

		/// <summary>
		/// Closes this vertex stream.
		/// </summary>
		public void Close() {
			Flush();
		}

		/// <summary>
		/// Begins the batch for stream.
		/// </summary>
		/// <param name="type">The type of primitives.</param>
		/// <param name="vertexCount">The vertex count requested.</param>
		/// <param name="indexCount">The index count requested.</param>
		void BeginBatch(PrimitiveType type, int vertexCount, int indexCount) {
			if (vertexCount > _maxVertexCount) {
				throw new ArgumentOutOfRangeException(string.Format("Vertex count {0} is greater than maximum allowed {1}", vertexCount, _maxVertexCount));
			}
			if (indexCount > _maxIndexCount) {
				throw new ArgumentOutOfRangeException(string.Format("Index count {0} is greater than maximum allowed {1}", indexCount, _maxIndexCount));
			}

			_vertexCount = _indexCount = 0;
			_primitiveType = type;
			_batchIsOpened = true;
		}

		/// <summary>
		/// Ends the batch.
		/// </summary>
		public void EndBatch() {
			if (_batchIsOpened) {
				DrawBatch();
				_batchIsOpened = false;
			}
		}

		/// <summary>
		/// Requests stream to allocate the vertex/index fragment.
		/// </summary>
		/// <param name="fragment">The fragment description.</param>
		/// <param name="location">The location of fragment in memory.</param>
		public unsafe void AllocateFragment(VertexFragment fragment, out FragmentLocation location) {
			//check should we open batch automatically
			if (_batchIsOpened &&
					(fragment.VertexCount + _vertexCount > _maxVertexCount ||
					 fragment.IndexCount + _indexCount > _maxIndexCount ||
					 fragment.PrimitiveType != _primitiveType)) {
				EndBatch();	//terminate current batch we can't fit data in it
			}
			//check if batch was closed for a some reasons, lets open new one, but use best size for it
			if (!_batchIsOpened) {
				BeginBatch(fragment.PrimitiveType, Math.Max(fragment.VertexCount, _maxVertexCount), Math.Max(fragment.IndexCount, _maxIndexCount));
			}

			//location = new FragmentLocation();
			//fill queryResult parameters
			location.Vertices = _vertexData + _vertexCount;
			location.Indices = _indexData + _indexCount;
			location.BaseIndex = (ushort)_vertexCount;

			//update internal counters
			_vertexCount += fragment.VertexCount;
			_indexCount += fragment.IndexCount;
		}

		/// <summary>
		/// Flushes this vertex stream cached data.
		/// </summary>
		public void Flush() {
			EndBatch();
		}

		#region Private Methods

		/// <summary>
		/// Initializes this vertex stream.
		/// </summary>
		private void Initialize() {
            //alloc memory for buffers
            _vertexData = (Vertex2D*)Marshal.AllocHGlobal(_maxVertexCount * sizeof(Vertex2D)).ToPointer();
            _indexData = (ushort*)Marshal.AllocHGlobal(_maxIndexCount * sizeof(ushort)).ToPointer();

            Log.Details("Size of VertexElement is " + sizeof(D3DVertexElement));
            HResult result = _d3dDevice.CreateVertexDeclaration(Vertex2D.DeclarationElements, out _vertexDeclaration);
            Watcher.AssertCritical(result, "Create primary vertex declaration");

			int vbSize = _maxVertexCount * sizeof(Vertex2D);
			int ixSize = _maxIndexCount * sizeof(ushort);

			result = _d3dDevice.CreateVertexBuffer((uint)vbSize, D3DUsage.WriteOnly | D3DUsage.Dynamic, D3DFVF.None, D3DPool.Default, out _vertexBuffer);
			Watcher.AssertCritical(result, "Create vertex buffer of size {0} bytes", vbSize);

			result = _d3dDevice.CreateIndexBuffer((uint)ixSize, D3DUsage.WriteOnly | D3DUsage.Dynamic, D3DFormat.INDEX16, D3DPool.Default, out _indexBuffer);
			Watcher.AssertCritical(result, "Create index buffer of size {0} bytes", ixSize);

		}

		/// <summary>
		/// Destroys vertex stream. Frees the internal data of component.
		/// </summary>
		public override void Destroy() {
            Marshal.FreeHGlobal(new IntPtr(_vertexData));
            Marshal.FreeHGlobal(new IntPtr(_indexData));

            _vertexDeclaration.Dispose();
			_vertexBuffer.Dispose();
			_indexBuffer.Dispose();

            _vertexDeclaration = null;
			_vertexBuffer = null;
			_indexBuffer = null;
		}

		private void CopyData(int vertexCount, int indexCount) {
			void* pVertexData;
			void* pIndexData;

#if PERF_STATS
			Device.PerfMonitor.StartTimer(DeviceTimer.BufferLock);
#endif

			HResult result = _vertexBuffer.Lock(0, (uint)(vertexCount * sizeof(Vertex2D)), out pVertexData, D3DLockFlags.Disacrd);
			Watcher.AssertCritical(result, "Lock vertex buffer");

			result = _indexBuffer.Lock(0, (uint)(indexCount * sizeof(ushort)), out pIndexData, D3DLockFlags.Disacrd);
			Watcher.AssertCritical(result, "Lock index buffer");

			Vertex2D* vertexData = (Vertex2D*)pVertexData;
			ushort* indexData = (ushort*)pIndexData;

			//do copying
			for (int n = 0; n < vertexCount; ++n) {
				vertexData[n] = _vertexData[n];
			}

			for (int n = 0; n < indexCount; ++n) {
				indexData[n] = _indexData[n];
			}

#if PERF_STATS
			Device.PerfMonitor.StopTimer(DeviceTimer.BufferLock);
#endif

#if PERF_STATS
			Device.PerfMonitor.StartTimer(DeviceTimer.BufferUnlock);
#endif
			//unlock buffers
			_vertexBuffer.Unlock();
			_indexBuffer.Unlock();

#if PERF_STATS
			Device.PerfMonitor.StopTimer(DeviceTimer.BufferUnlock);
#endif
		}

		/// <summary>
		/// Draws current batch.
		/// </summary>
		private void DrawBatch() {
			CopyData(_vertexCount, _indexCount);
			Draw(_primitiveType, _vertexCount, _indexCount, CalculatePrimitiveCount(_primitiveType, _indexCount));
		}

		/// <summary>
		/// Calculates the primitive count based on index count primitive type.
		/// </summary>
		/// <param name="primitiveType">Type of the primitive.</param>
		/// <param name="indexCount">The index count.</param>
		/// <returns></returns>
		public static int CalculatePrimitiveCount(PrimitiveType primitiveType, int indexCount) {
			switch (primitiveType) {
				case PrimitiveType.PointList:
					return indexCount;
				case PrimitiveType.LineList:
					return indexCount / 2;
				case PrimitiveType.TriangleList:
					return indexCount / 3;
				case PrimitiveType.TriangleFan:
				case PrimitiveType.TriangleStrip:
					return indexCount - 1;
				default:
					return 0;
			}
		}

		/// <summary>
		/// Wraps the primitive drawing
		/// </summary>
		/// <param name="primitiveType">Type of the primitives to draw.</param>
		/// <param name="vertexCount">The vertex count to draw.</param>
		/// <param name="primitiveCount">The primitive count to draw.</param>
		private void Draw(PrimitiveType primitiveType, int vertexCount, int indexCount, int primitiveCount) {
#if PERF_STATS
			Device.PerfMonitor.StartTimer(DeviceTimer.Drawing);
#endif
			//Log.Details("V: {0}, I: {1}, P: {2}", vertexCount, indexCount, primitiveCount);

			HResult result = _d3dDevice.DrawIndexedPrimitive((D3DPrimitiveType)primitiveType, 0, 0, (uint)vertexCount, 0, (uint)primitiveCount);
			Watcher.AssertCritical(result, "DrawIndexedPrimitive failed for type {0} and {1} vertices, {2} primitives", primitiveType, vertexCount, primitiveCount);
			//_d3dDevice.DebugDraw();

#if PERF_STATS
			Device.PerfMonitor.AddCounter(DeviceCounter.VerticesDrawn, (int)vertexCount);
			Device.PerfMonitor.AddCounter(DeviceCounter.IndicesDrawn, (int)indexCount);
			Device.PerfMonitor.AddCounter(DeviceCounter.PrimitivesDrawn, (int)primitiveCount);
			Device.PerfMonitor.AddCounter(DeviceCounter.BatchesRendered);
			Device.PerfMonitor.StopTimer(DeviceTimer.Drawing);
#endif
		}

		#endregion

		/// <summary>
		/// Sets the batch size hint.
		/// </summary>
		/// <param name="batchSizeHint">The batch size hint.</param>
		public void SetBatchSizeHint(int batchSizeHint) {
			;
		}

		protected override void BeforeDeviceReset() {
			Destroy();
		}

		protected override void AfterDeviceReset() {
			Initialize();
		}
	}

}
